/*******************************************************************************
 * Copyright (c) 2007-2012 LuaJ. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2;


import java.lang.ref.WeakReference;

/**
 * Subclass of {@link LuaValue} that implements
 * a lua coroutine thread using Java Threads.
 * <p>
 * A LuaThread is typically created in response to a scripted call to
 * {@code coroutine.create()}
 * <p>
 * The threads must be initialized with the globals, so that
 * the global environment may be passed along according to rules of lua.
 * This is done via a call to {@link #setGlobals(LuaValue)}
 * at some point during globals initialization.
 * See {@link BaseLib} for additional documentation and example code.
 * <p>
 * The utility classes {@link JsePlatform} and {@link JmePlatform}
 * see to it that this initialization is done properly.
 * For this reason it is highly recommended to use one of these classes
 * when initializing globals.
 * <p>
 * The behavior of coroutine threads matches closely the behavior
 * of C coroutine library.  However, because of the use of Java threads
 * to manage call state, it is possible to yield from anywhere in luaj.
 * <p>
 * Each Java thread wakes up at regular intervals and checks a weak reference
 * to determine if it can ever be resumed.  If not, it throws
 * {@link OrphanedThread} which is an {@link Error}.
 * Applications should not catch {@link OrphanedThread}, because it can break
 * the thread safety of luaj.
 *
 * @see LuaValue
 */
public class LuaThread extends LuaValue {

    public static LuaValue s_metatable;

    static int coroutine_count = 0;

    /**
     * Interval at which to check for lua threads that are no longer referenced.
     * This can be changed by Java startup code if desired.
     */
    static long thread_orphan_check_interval = 30000;

    static int STATUS_INITIAL = 0;
    static int STATUS_SUSPENDED = 1;
    static int STATUS_RUNNING = 2;
    static int STATUS_NORMAL = 3;
    static int STATUS_DEAD = 4;
    static String[] STATUS_NAMES = {
            "suspended",
            "suspended",
            "running",
            "normal",
            "dead",};

    public State state;

//	public static  int MAX_CALLSTACK = 256; remove by yanqiu

    /** Interval to check for LuaThread dereferencing.  */
//	public static int GC_INTERVAL = 30000; remove by yanqiu

    /**
     * Thread-local used by DebugLib to store debugging state.
     * This is ano opaque value that should not be modified by applications.
     */
    public Object callstack;

    public Globals globals;

    /**
     * Hook function control state used by debug lib.
     */
    public LuaValue hookfunc;

    /**
     * Error message handler for this thread, if any.
     */
    public LuaValue errorfunc;

    public boolean hookline;
    public boolean hookcall;
    public boolean hookrtrn;
    public int hookcount;
    public boolean inhook;
    public int lastline;
    public int bytecodes;


    /**
     * Private constructor for main thread only
     */
    public LuaThread(Globals globals) {
        state = new State(globals, this, null);
        state.status = STATUS_RUNNING;
        this.globals = globals;
    }

    /**
     * Create a LuaThread around a function and environment
     *
     * @param func The function to execute
     */
    public LuaThread(Globals globals, LuaValue func) {
        LuaValue.assert_(func != null, "function cannot be null");
        state = new State(globals, this, func);
        this.globals = globals;
    }

    public int type() {
        return LuaValue.TTHREAD;
    }

    public String typename() {
        return "thread";
    }

    public boolean isthread() {
        return true;
    }

    public LuaThread optthread(LuaThread defval) {
        return this;
    }

    public LuaThread checkthread() {
        return this;
    }

    public LuaValue getmetatable() {
        return s_metatable;
    }

    public String getStatus() {
        return STATUS_NAMES[state.status];
    }

    public boolean isMainThread() {
        return this.state.function == null;
    }

    public Varargs resume(Varargs args) {
        LuaThread.State s = this.state;
        if (s.status > LuaThread.STATUS_SUSPENDED)
            return LuaValue.varargsOf(LuaValue.FALSE,
                    LuaValue.valueOf("cannot resume " + (s.status == LuaThread.STATUS_DEAD ? "dead" : "non-suspended") + " coroutine"));
        return s.lua_resume(this, args);
    }

    static class State implements Runnable {
        private Globals globals;
        WeakReference lua_thread;
        public LuaValue function;
        Varargs args = LuaValue.NONE;
        Varargs result = LuaValue.NONE;
        String error = null;
        public int status = LuaThread.STATUS_INITIAL;

        State(Globals globals, LuaThread lua_thread, LuaValue function) {
            this.globals = globals;
            this.lua_thread = new WeakReference(lua_thread);
            this.function = function;
        }

        public synchronized void run() {
            try {
                Varargs a = this.args;
                this.args = LuaValue.NONE;
                this.result = function.invoke(a);
            } catch (Throwable t) {
                this.error = t.getMessage();
            } finally {
                this.status = LuaThread.STATUS_DEAD;
                this.notify();
            }
        }

        synchronized Varargs lua_resume(LuaThread new_thread, Varargs args) {
            LuaThread previous_thread = globals.running;
            try {
                globals.running = new_thread;
                this.args = args;
                if (this.status == STATUS_INITIAL) {
                    this.status = STATUS_RUNNING;
                    new Thread(this, "Coroutine-" + (++coroutine_count)).start();
                } else {
                    this.notify();
                }
                if (previous_thread != null)
                    previous_thread.state.status = STATUS_NORMAL;
                this.status = STATUS_RUNNING;
                this.wait();
                return (this.error != null ?
                        LuaValue.varargsOf(LuaValue.FALSE, LuaValue.valueOf(this.error)) :
                        LuaValue.varargsOf(LuaValue.TRUE, this.result));
            } catch (InterruptedException ie) {
                throw new OrphanedThread();
            } finally {
                this.args = LuaValue.NONE;
                this.result = LuaValue.NONE;
                this.error = null;
                globals.running = previous_thread;
                if (previous_thread != null)
                    globals.running.state.status = STATUS_RUNNING;
            }
        }

        synchronized Varargs lua_yield(Varargs args) {
            try {
                this.result = args;
                this.status = STATUS_SUSPENDED;
                this.notify();
                do {
                    this.wait(thread_orphan_check_interval);
                    if (this.lua_thread.get() == null) {
                        this.status = STATUS_DEAD;
                        throw new OrphanedThread();
                    }
                } while (this.status == STATUS_SUSPENDED);
                return this.args;
            } catch (InterruptedException ie) {
                this.status = STATUS_DEAD;
                throw new OrphanedThread();
            } finally {
                this.args = LuaValue.NONE;
                this.result = LuaValue.NONE;
            }
        }
    }

}
